#pragma once

#include <iostream>
#include <string>
#include <tuple>
#include <initializer_list>
#include <mutex>
#include <cerrno>
#include <cstring>
#include <cstdio>
#include <ctime>

enum class level : size_t
{
    DEBUG,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

class my_log
{
public:
    static constexpr const char* const _lvToStr[5] = { "DEBUG", "INFO", "WARNING", "ERROR", "FATAL"};
private:
    static char _cur_time[100];
public:
    my_log(FILE* const dst = stdout) noexcept
        : _dst_file(dst)
        , _is_end(true)
    {}

    my_log(const int dstno)
        : _is_end(true)
    {
        _dst_file = fdopen(dstno, "a");
        if (_dst_file == nullptr)
        {
            perror("log creation failed");
            printf("\n");
            std::terminate();
        }
    }

    ~my_log()
    {
        if (fclose(_dst_file))
        {
            perror("log destruction failed");
            printf("\n");
            std::terminate();
        }
    }

    template<class... Args_meg>
    void basic_print(level lv, std::tuple<Args_meg...>&& args_meg, bool is_end, bool is_err = false)
    {
        std::lock_guard<std::mutex> l(_m);
        _basic_print(lv, args_meg, is_end, is_err, std::make_index_sequence<sizeof...(Args_meg)>());
    }

    template<class... Args_meg, class... Args_files>
    void print(level lv, std::tuple<Args_meg...>&& args_meg, std::tuple<Args_files...>&& args_files, 
               bool is_end, bool is_err = false)
    {
        std::lock_guard<std::mutex> l(_m);
        FILE* tem = _dst_file;
        _print(lv, args_meg, args_files, is_end, is_err, std::make_index_sequence<sizeof...(Args_files)>());
        _dst_file = tem;
    }
private:
    template<class... Args_meg, class... Args_files, size_t... indices>
    void _print(level lv, std::tuple<Args_meg...>& args_meg, std::tuple<Args_files...>& args_files, 
                bool is_end, bool is_err, std::index_sequence<indices...>)
    {
        std::initializer_list<FILE*> file_ini_list = {convert_fd_to_FILE_ptr(std::get<indices>(args_files))...};
        for (auto& file : file_ini_list)
        {
            _dst_file = file;
            _basic_print(lv, args_meg, is_end, is_err, std::make_index_sequence<sizeof...(Args_meg)>());
        }
    }

    template<class... Args_meg, size_t... indices>
    void _basic_print(level lv, std::tuple<Args_meg...>& args_meg, bool is_end, 
                      bool is_err, std::index_sequence<indices...>)
    {
        my_log::get_time();
        if (is_err)
        {
            if (_is_end)
            {
                fprintf(_dst_file, "[%s] [%s] ", _cur_time, _lvToStr[static_cast<size_t>(lv)]);
                _is_end = false;
            }
            fprintf(_dst_file, std::get<indices>(args_meg)...);
            if (is_end)
            {
                fprintf(_dst_file, "%s\n", strerror(errno));
                _is_end = true;
            }
            else
            {
                fprintf(_dst_file, "%s", strerror(errno));
            }
        }
        else
        {
            if (_is_end)
            {
                fprintf(_dst_file, "[%s] [%s] ", _cur_time, _lvToStr[static_cast<size_t>(lv)]);
                _is_end = false;
            }
            fprintf(_dst_file, std::get<indices>(args_meg)...);
            if (is_end)
            {
                fprintf(_dst_file, "\n");
                _is_end = true;
            }
        }
    }

    FILE* convert_fd_to_FILE_ptr(int fd)
    {
        FILE* f = fdopen(fd, "a");
        if (f == nullptr)
        {
            perror("file expand filed");
            std::terminate();
        } 
        return f;  
    }

    FILE* convert_fd_to_FILE_ptr(FILE* t) noexcept
    {
        return t;
    }

    static inline void get_time()
    {
        time_t rawtime;  
        time(&rawtime);  
        struct tm* timeinfo = localtime(&rawtime); 
        strftime(_cur_time, sizeof(_cur_time), "%Y-%m-%d %H:%M:%S", timeinfo);
    }
private:
    FILE* _dst_file;
    std::mutex _m;
    bool _is_end;
};

char my_log::_cur_time[100] = {};

static my_log lg;